package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.pojo.RequestParams;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {


    @Resource
    private RestHighLevelClient client;

    @Override
    public PageResult search(RequestParams params) throws IOException {
        //1.准备Request
        SearchRequest request = new SearchRequest("hotel");

        buildBasicQuery(params, request);


        Integer size = params.getSize();
        Integer page = params.getPage();
        request.source().from((page - 1) * size).size(size);


        //安地理位置排序
        if (params.getLocation() != null && !"".equals(params.getLocation())) {
            request.source().sort(SortBuilders.geoDistanceSort("location", new GeoPoint(params.getLocation()))
                    .order(SortOrder.ASC)
                    .unit(DistanceUnit.KILOMETERS));
        }
        if (params.getSortBy().equals("score")) {
            request.source().sort("score", SortOrder.DESC);
        }
        if (params.getSortBy().equals("price")) {
            request.source().sort("price", SortOrder.DESC);
        }

        //3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);


        //4.解析响应
        return handleResponse(response);

    }

    @Override
    public Map<String, List<String>> getFilters(RequestParams params) {
        try {
            //1.准备request
            SearchRequest request = new SearchRequest("hotel");

            //2.准备DSL
            //2.1 query
            buildBasicQuery(params, request);
            //2.2 设置size
            request.source().size(0);
            //2.3聚合
            request.source().aggregation(AggregationBuilders.terms("cityAgg").field("city").size(100));
            request.source().aggregation(AggregationBuilders.terms("starAgg").field("starName").size(100));
            request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(100));


            //3.发送请求
            SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
            //4.解析响应
            Map<String, List<String>> resultMap = new HashMap<>();
            Aggregations aggregations = searchResponse.getAggregations();
            //4.1根据城市名称，获取城市结果
            List<String> cityList = getAggName(aggregations, "cityAgg");
            resultMap.put("city", cityList);
            //4.2根据星级名称，获取星级结果
            List<String> starList = getAggName(aggregations, "starAgg");
            resultMap.put("starName", starList);
            //4.3根据品牌名称，获取品牌结果
            List<String> brandList = getAggName(aggregations, "brandAgg");
            resultMap.put("brand", brandList);

            return resultMap;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<String> suggestion(String key) throws IOException {
        //1.准备request
        SearchRequest request = new SearchRequest("hotel");

        //2.准备DSL
        request.source().suggest(new SuggestBuilder().addSuggestion(
                "suggestions",
                SuggestBuilders.completionSuggestion("suggestion")
                        .prefix(key)
                        .skipDuplicates(true)
                        .size(10)
        ));

        //3.发送请求
        SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);

        //4.结解析请求
        Suggest suggest = searchResponse.getSuggest();
        CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
        List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
        List<String> list = new ArrayList<>();
        for (CompletionSuggestion.Entry.Option option : options) {
            String text = option.getText().toString();
            list.add(text);
        }
        return list;
    }

    @Override
    public void deleteById(Long id) {
        try {
            //1，创建Request对象
            DeleteRequest request = new DeleteRequest("hotel", id.toString());

            //2.发送请求
            client.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void insetById(Long id) {
        try {
            //0.根据id查询酒店数据
            Hotel hotel = getById(id);

            //转化为文档类型
            HotelDoc hotelDoc = new HotelDoc(hotel);


            //1，创建Request对象
            IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());

            //2.准备json文档
            request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);

            //3.发送请求
            client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

        private List<String> getAggName (Aggregations aggregations, String aggName){
            List<String> list = new ArrayList<>();
            Terms cityAgg = aggregations.get(aggName);
            List<? extends Terms.Bucket> buckets = cityAgg.getBuckets();
            for (Terms.Bucket bucket : buckets) {
                String key = bucket.getKeyAsString();
                list.add(key);
            }

            return list;
        }

        private static void buildBasicQuery (RequestParams params, SearchRequest request){
            //2.准备DSL
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

            //关键字搜索 must
            String key = params.getKey();
            if (key == null || "".equals(key)) {
                boolQuery.must(QueryBuilders.matchAllQuery());
            } else {
                boolQuery.must(QueryBuilders.matchQuery("all", key));
            }

            //条件过滤
            //城市
            if (params.getCity() != null && !"".equals(params.getCity())) {
                boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
            }

            //品牌
            if (params.getBrand() != null && !"".equals(params.getBrand())) {
                boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
            }

            //星际
            if (params.getStarName() != null && !"".equals(params.getStarName())) {
                boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
            }

            //价格
            if (params.getMinPrice() != null && params.getMaxPrice() != null) {
                boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice())
                        .lte(params.getMaxPrice()));
            }

            //算分控制
            FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(boolQuery,  //原始查询。相关性算分查询
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{ //其中一个function source元素
                            new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                    //过滤条件
                                    QueryBuilders.termQuery("isAD", true),
                                    //算分函数
                                    ScoreFunctionBuilders.weightFactorFunction(10)
                            )
                    }
            );

            //放入source中查询
            request.source().query(functionScoreQuery);
        }

        private PageResult handleResponse (SearchResponse response){
            PageResult pageResult = new PageResult();
            List<HotelDoc> list = new ArrayList<>();
            SearchHits searchHits = response.getHits();

            long total = searchHits.getTotalHits().value;
            pageResult.setTotal(total);
            log.info("共搜索到" + total + "条数据");

            SearchHit[] hits = searchHits.getHits();
            for (SearchHit hit : hits) {
                String json = hit.getSourceAsString();
                HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);

                //获取排名
                Object[] sortValues = hit.getSortValues();
                if (sortValues.length > 0) {
                    Object sortValue = sortValues[0];
                    hotelDoc.setDistance(sortValue);
                }
                list.add(hotelDoc);
            }
            pageResult.setHotels(list);
            return pageResult;
        }


    }
